package com.symaster.common.geom.nest4j;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.symaster.common.geom.nest4j.algorithm.GeneticAlgorithm;
import com.symaster.common.geom.nest4j.algorithm.Individual;
import com.symaster.common.geom.nest4j.data.Vector;
import com.symaster.common.geom.nest4j.data.*;
import com.symaster.common.geom.nest4j.util.*;

import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


/**
 * @author yisa
 */
public class Nest {
    private static final Gson gson = new GsonBuilder().create();
    private final Map<String, List<NestPath>> nfpCache;
    int loopCount;
    private NestPath binPath;
    private List<NestPath> parts;
    private Config config;
    private GeneticAlgorithm GA = null;
    private int launchCount = 0;
    private static final ThreadPoolExecutor nfpExec = new ThreadPoolExecutor(5, 5, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(4096));

    /**
     * 创建一个新的Nest对象
     *
     * @param binPath 底板多边形
     * @param parts   板件多边形列表
     * @param config  参数设置
     * @param count   迭代计算次数
     */
    public Nest(NestPath binPath, List<NestPath> parts, Config config, int count) {
        this.binPath = binPath;
        this.parts = parts;
        this.config = config;
        this.loopCount = count;
        nfpCache = new HashMap<>();
    }

    /**
     * 通过id与bid将translate和rotate绑定到对应板件上
     */
    public static List<List<Placement>> applyPlacement(Result best, List<NestPath> tree) {
        List<List<Placement>> applyPlacement = new ArrayList<>();
        for (int i = 0; i < best.placements.size(); i++) {
            List<Placement> binTranslate = new ArrayList<>();
            for (int j = 0; j < best.placements.get(i).size(); j++) {
                Vector v = best.placements.get(i).get(j);
                NestPath nestPath = tree.get(v.id);
                for (NestPath child : nestPath.getChildren()) {
                    Placement chPlacement = new Placement(child.bid, new Segment(v.x, v.y), v.rotation);
                    binTranslate.add(chPlacement);
                }
                Placement placement = new Placement(nestPath.bid, new Segment(v.x, v.y), v.rotation);
                binTranslate.add(placement);
            }
            applyPlacement.add(binTranslate);
        }
        return applyPlacement;
    }

    /**
     * 在遗传算法中每次突变或者是交配产生出新的种群时，可能会出现板件与旋转角度不适配的结果，需要重新检查并适配。
     */
    private static List<Integer> checkIfCanBePlaced(NestPath binPolygon, List<NestPath> tree) {
        List<Integer> CanBePlacdPolygonIndex = new ArrayList<>();
        Bound binBound = GeometryUtil.getPolygonBounds(binPolygon);
        for (int i = 0; i < tree.size(); i++) {
            NestPath nestPath = tree.get(i);
            if (nestPath.getRotation() == 0) {
                Bound bound = GeometryUtil.getPolygonBounds(nestPath);
                if (bound.width < binBound.width && bound.height < binBound.height) {
                    CanBePlacdPolygonIndex.add(i);
                    continue;
                }
            } else {
                for (int j = 0; j < nestPath.getRotation(); j++) {
                    Bound rotatedBound = GeometryUtil.rotatePolygon(nestPath, (360 / nestPath.getRotation()) * j);
                    if (rotatedBound.width < binBound.width && rotatedBound.height < binBound.height) {
                        CanBePlacdPolygonIndex.add(i);
                        break;
                    }
                }
            }
        }
        return CanBePlacdPolygonIndex;
    }

    /**
     * 开始进行Nest计算
     */
    public List<List<Placement>> startNest() {

        List<NestPath> tree = CommonUtil.BuildTree(parts, Config.CURVE_TOLERANCE);

        CommonUtil.offsetTree(tree, 0.5 * config.SPACING);

        binPath.config = config;
        for (NestPath nestPath : parts) {
            nestPath.config = config;
        }
        NestPath binPolygon = NestPath.cleanNestPath(binPath);
        assert binPolygon != null;
        Bound binBound = GeometryUtil.getPolygonBounds(binPolygon);
        if (config.SPACING > 0) {
            List<NestPath> offsetBin = CommonUtil.polygonOffset(binPolygon, -0.5 * config.SPACING);
            if (offsetBin.size() == 1) {
                binPolygon = offsetBin.get(0);
            }
        }
        binPolygon.setId(-1);

        List<Integer> integers = checkIfCanBePlaced(binPolygon, tree);
        List<NestPath> safeTree = new ArrayList<>();
        for (Integer i : integers) {
            safeTree.add(tree.get(i));
        }
        tree = safeTree;

        double xbinmax = binPolygon.get(0).x;
        double xbinmin = binPolygon.get(0).x;
        double ybinmax = binPolygon.get(0).y;
        double ybinmin = binPolygon.get(0).y;

        for (int i = 1; i < binPolygon.size(); i++) {
            if (binPolygon.get(i).x > xbinmax) {
                xbinmax = binPolygon.get(i).x;
            } else if (binPolygon.get(i).x < xbinmin) {
                xbinmin = binPolygon.get(i).x;
            }

            if (binPolygon.get(i).y > ybinmax) {
                ybinmax = binPolygon.get(i).y;
            } else if (binPolygon.get(i).y < ybinmin) {
                ybinmin = binPolygon.get(i).y;
            }
        }
        for (int i = 0; i < binPolygon.size(); i++) {
            binPolygon.get(i).x -= xbinmin;
            binPolygon.get(i).y -= ybinmin;
        }


        double binPolygonWidth = xbinmax - xbinmin;
        double binPolygonHeight = ybinmax - ybinmin;

        if (GeometryUtil.polygonArea(binPolygon) > 0) {
            binPolygon.reverse();
        }

        /* 确保为逆时针 */
        for (NestPath nestPath : tree) {
            Segment start = nestPath.get(0);
            Segment end = nestPath.get(nestPath.size() - 1);
            if (start == end || GeometryUtil.almostEqual(start.x, end.x) && GeometryUtil.almostEqual(start.y, end.y)) {
                nestPath.pop();
            }
            if (GeometryUtil.polygonArea(nestPath) > 0) {
                nestPath.reverse();
            }
        }

        launchCount = 0;
        Result best = null;
        for (int i = 0; i < loopCount; i++) {

            Result result = launchWorkers(tree, binPolygon, config);

            if (i == 0) {
                best = result;
            } else {
                if (best.fitness > result.fitness) {
                    best = result;
                }
            }
        }
        double sumarea = 0;
        double totalarea = 0;
        for (int i = 0; i < best.placements.size(); i++) {
            totalarea += Math.abs(GeometryUtil.polygonArea(binPolygon));
            for (int j = 0; j < best.placements.get(i).size(); j++) {
                sumarea += Math.abs(GeometryUtil.polygonArea(tree.get(best.placements.get(i).get(j).id)));
            }
        }
        double rate = (sumarea / totalarea) * 100;
        return applyPlacement(best, tree);
    }

    /**
     * 一次迭代计算
     *
     * @param tree       底板
     * @param binPolygon 板件列表
     * @param config     设置
     */
    public Result launchWorkers(List<NestPath> tree, NestPath binPolygon, Config config) {
        launchCount++;
        if (GA == null) {

            List<NestPath> adam = new ArrayList<>();
            for (NestPath nestPath : tree) {
                NestPath clone = new NestPath(nestPath);
                adam.add(clone);
            }
            for (NestPath nestPath : adam) {
                nestPath.area = GeometryUtil.polygonArea(nestPath);
            }
            Collections.sort(adam);
            GA = new GeneticAlgorithm(adam, binPolygon, config);
        }

        GA.generation(); // 执行遗传算法

        List<Double> collect = GA.population.stream().map(Individual::getFitness).collect(Collectors.toList());
        System.out.println("getFitness = " + collect);

        System.out.println("GA.population.size() = " + GA.population.size());

        Result bestResult = null;

        for (Individual individual : GA.population) {
            if (individual.getFitness() >= 0.0D) {
                continue;
            }

            List<NestPath> placeList = individual.getPlacement();
            List<Integer> rotations = individual.getRotation();

            List<Integer> ids = new ArrayList<>();
            for (int i = 0; i < placeList.size(); i++) {
                ids.add(placeList.get(i).getId());
                placeList.get(i).setRotation(rotations.get(i));
            }
            List<NfpPair> nfpPairs = new ArrayList<>();
            NfpKey key;

            /* 如果在nfpCache里没找到nfpKey 则添加进nfpPairs */
            for (int i = 0; i < placeList.size(); i++) {
                NestPath part = placeList.get(i);
                key = new NfpKey(binPolygon.getId(), part.getId(), true, 0, part.getRotation());
                nfpPairs.add(new NfpPair(binPolygon, part, key));
                for (int j = 0; j < i; j++) {
                    NestPath placed = placeList.get(j);
                    NfpKey keyed = new NfpKey(placed.getId(), part.getId(), false, rotations.get(j), rotations.get(i));
                    nfpPairs.add(new NfpPair(placed, part, keyed));
                }
            }

            ParallelData[] generatedNfp = nfpGeneratorThreadPool(config, nfpPairs);
            // List<ParallelData> generatedNfp = nfpGenerator(config, nfpPairs);

            for (ParallelData Nfp : generatedNfp) {
                // TODO remove gson & generate a new key algorithm
                nfpCache.put(gson.toJson(Nfp.getKey()), Nfp.value);
            }

            Placementworker worker = new Placementworker(binPolygon, config, nfpCache);
            List<NestPath> placeListSlice = new ArrayList<>();

            for (NestPath nestPath : placeList) {
                placeListSlice.add(new NestPath(nestPath));
            }

            List<List<NestPath>> data = new ArrayList<>();
            data.add(placeListSlice);

            List<Result> placements = new ArrayList<>();
            for (List<NestPath> datum : data) {
                Result result = worker.placePaths(datum);
                placements.add(result);
            }

            individual.fitness = placements.get(0).fitness;
            System.out.println("individual.fitness = " + individual.fitness);

            if (bestResult == null) {
                bestResult = placements.get(0);
            }

            for (Result placement : placements) {
                if (placement.fitness < bestResult.fitness) {
                    bestResult = placement;
                }
            }

            // for (int i = 1; i < placements.size(); i++) {
            //     if (placements.get(i).fitness < bestResult.fitness) {
            //         bestResult = placements.get(i);
            //     }
            // }
        }

        System.out.println("Over");
        return bestResult;

        // Individual individual = null;
        // for (Individual individual1 : GA.population) {
        //     if (individual1.getFitness() < 0) {
        //         individual = individual1;
        //         break;
        //     }
        // }
        //
        // if (launchCount > 1 && individual == null) {
        //     GA.generation();
        //     individual = GA.population.get(1);
        // }

        // 以上为GA

        // assert individual != null;

    }

    /**
     * 多线程创建Nfp
     */
    private ParallelData[] nfpGeneratorThreadPool(Config config, List<NfpPair> nfpPairs) {
        /* 第一次nfpCache为空 ，nfpCache存的是nfpKey所对应的两个polygon所形成的Nfp( List<NestPath> ) */
        ParallelData[] generatedNfp = new ParallelData[nfpPairs.size()];
        int[] status = new int[nfpPairs.size()];
        Arrays.fill(status, 0);

        System.out.printf("Nfp计算数量: %s%n", status.length);

        long s = System.currentTimeMillis();
        for (int i = 0; i < nfpPairs.size(); i++) {
            nfpExec.execute(new NfpRunnable(nfpPairs, generatedNfp, status, config, i));
        }

        try {
            while (waitOver(status)) {
                TimeUnit.MILLISECONDS.sleep(100);
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        long e = System.currentTimeMillis();
        System.out.printf("Nfp计算耗时: %.2fS%n", (e - s) / 1000f);
        return generatedNfp;
    }

    /**
     * 单线程创建Nfp
     */
    private static List<ParallelData> nfpGenerator(Config config, List<NfpPair> nfpPairs) {
        List<ParallelData> generatedNfp = new ArrayList<>();
        for (NfpPair nfpPair : nfpPairs) {
            ParallelData data = NfpUtil.nfpGenerator(nfpPair, config);
            generatedNfp.add(data);
        }
        return generatedNfp;
    }

    private boolean waitOver(int[] status) {
        for (int i : status) {
            if (i == 0) {
                return true;
            }
            if (i == 2) {
                throw new RuntimeException("Nfp计算出现异常");
            }
        }
        return false;
    }

    public void add(NestPath np) {
        parts.add(np);
    }

    public NestPath getBinPath() {
        return binPath;
    }

    public void setBinPath(NestPath binPath) {
        this.binPath = binPath;
    }

    public List<NestPath> getParts() {
        return parts;
    }

    public void setParts(List<NestPath> parts) {
        this.parts = parts;
    }

    public Config getConfig() {
        return config;
    }

    public void setConfig(Config config) {
        this.config = config;
    }

    private static class NfpRunnable implements Runnable {
        private final List<NfpPair> nfpPairs;
        private final int index;
        private final ParallelData[] nfpResult;
        private final int[] execStatus; // 0:未完成 1:已完成 2:出现异常
        private final Config config;

        public NfpRunnable(List<NfpPair> nfpPairs, ParallelData[] nfpResult, int[] execStatus, Config config, int index) {
            this.nfpPairs = nfpPairs;
            this.index = index;
            this.nfpResult = nfpResult;
            this.execStatus = execStatus;
            this.config = config;
        }

        @Override
        public void run() {
            try {
                nfpResult[index] = NfpUtil.nfpGenerator(nfpPairs.get(index), config);
            } finally {
                execStatus[index] = 1;
            }
        }
    }
}
